package gameoflife.automaton;

import gameoflife.Heading;
import gameoflife.Position;
import gameoflife.Rule;

import java.util.Map;

/**
 * Represents a 4x4 bit matrix.
 * 
 * <pre>
 * 00 01 02 03 | 0x0001 0x0002 0x0004 0x0008
 * 04 05 06 07 | 0x0010 0x0020 0x0040 0x0080
 * 08 09 10 11 | 0x0100 0x0200 0x0400 0x0800
 * 12 13 14 15 | 0x1000 0x2000 0x4000 0x8000
 * </pre>
 */
public class BitBlock {

	// 32-bit integers avoid signed arithmetic of shorts
	protected int matrix;
	protected int next;

	protected BitBlock N, NE, E, SE, S, SW, W, NW;

	protected byte births;
	protected byte deaths;

	public BitBlock(Map<Position, BitBlock> map, Position position) {
		final Position to = new Position();

		N = map.get(Heading.N.toPosition(position, to));
		E = map.get(Heading.E.toPosition(position, to));
		S = map.get(Heading.S.toPosition(position, to));
		W = map.get(Heading.W.toPosition(position, to));
		NE = map.get(Heading.NE.toPosition(position, to));
		SE = map.get(Heading.SE.toPosition(position, to));
		SW = map.get(Heading.SW.toPosition(position, to));
		NW = map.get(Heading.NW.toPosition(position, to));

		if (N != null) {
			N.S = this;
		}
		if (E != null) {
			E.W = this;
		}
		if (S != null) {
			S.N = this;
		}
		if (W != null) {
			W.E = this;
		}
		if (NE != null) {
			NE.SW = this;
		}
		if (SE != null) {
			SE.NW = this;
		}
		if (SW != null) {
			SW.NE = this;
		}
		if (NW != null) {
			NW.SE = this;
		}
	}

	/**
	 * Compute the next state for each of the 16 cells.
	 * <pre>
	 * 0x8000 | 0x1000 0x2000 0x4000 0x8000 | 0x1000
	 * ---------------------------------------------
	 * 0x0008 | 0x0001 0x0002 0x0004 0x0008 | 0x0001
	 * 0x0080 | 0x0010 0x0020 0x0040 0x0080 | 0x0010
	 * 0x0800 | 0x0100 0x0200 0x0400 0x0800 | 0x0100
	 * 0x8000 | 0x1000 0x2000 0x4000 0x8000 | 0x1000
	 * ---------------------------------------------
	 * 0x0008 | 0x0001 0x0002 0x0004 0x0008 | 0x0001
	 * </pre>
	 */
	public final void computeNext() {
		// inner four bits
		next = lookup0x0660[matrix];

		// outer twelve bits
		int n = N == null ? 0 : N.matrix;
		int e = E == null ? 0 : E.matrix;
		int s = S == null ? 0 : S.matrix;
		int w = W == null ? 0 : W.matrix;
		int ne = NE == null ? 0 : NE.matrix;
		int se = SE == null ? 0 : SE.matrix;
		int sw = SW == null ? 0 : SW.matrix;
		int nw = NW == null ? 0 : NW.matrix;

		int bits0x0007 = (matrix & 0x00FF) | (n & 0xF000) | ((nw & 0x8000) >> 4) | ((w & 0x0080) << 2) | ((w & 0x0008) << 5);
		int bits0x0888 = (matrix & 0xCCCC) | (e & 0x1111) | ((ne & 0x1000) >> 7) | ((n & 0x4000) >> 5) | ((n & 0x8000) >> 2);
		int bits0xE000 = (matrix & 0xFF00) | (s & 0x000F) | ((se & 0x0001) << 4) | ((e & 0x1000) >> 5) | ((e & 0x0100) >> 2);	
		int bits0x1110 = (matrix & 0x3333) | (w & 0x8888) | ((sw & 0x0008) << 7) | ((s & 0x0001) << 2) | ((s & 0x0002) << 5);

		next |= lookup0x0007[bits0x0007] | lookup0x0888[bits0x0888] | lookup0xE000[bits0xE000] | lookup0x1110[bits0x1110];

		// book-keeping
		int survivors = next & matrix;
		births = bitCounts[survivors ^ next];
		deaths = bitCounts[survivors ^ matrix];
	}

	public final void flip() {
		matrix = next;
	}

	public final boolean isDisposable() {
		return matrix == 0 &&
	       (N == null || N.matrix == 0) && (NE == null || NE.matrix == 0) &&
	       (E == null || E.matrix == 0) && (SE == null || SE.matrix == 0) &&
	       (S == null || S.matrix == 0) && (SW == null || SW.matrix == 0) &&
	       (W == null || W.matrix == 0) && (NW == null || NW.matrix == 0);
	}

	public final void dispose() {
		if (N != null) {
			N = N.S = null;
		}
		if (NE != null) {
			NE = NE.SW = null;
		}
		if (E != null) {
			E = E.W = null;
		}
		if (SE != null) {
			SE = SE.NW = null;
		}
		if (S != null) {
			S = S.N = null;
		}
		if (SW != null) {
			SW = SW.NE = null;
		}
		if (W != null) {
			W = W.E = null;
		}
		if (NW != null) {
			NW = NW.SE = null;
		}
	}

	@Override
	public String toString() {
		return Integer.toHexString(matrix);
	}

	// Bit counts lookup.
	private static final byte[] bitCounts = new byte[0xFFFF + 1];
	static {
		for (int i = 0; i <= 0xFFFF; i++) {
			bitCounts[i] = (byte) Integer.bitCount(i);
		}
	}

	// Current lookup rule.
	private static Rule lookupRule;

	// Lookup for inner 4 bits.
	private static final int[] lookup0x0660 = new int[0xFFFF + 1];

	// Lookup for the outer 12 bits.
	private static final int[] lookup0x0007 = new int[0xFFFF + 1];
	private static final int[] lookup0x0888 = new int[0xFFFF + 1];
	private static final int[] lookup0xE000 = new int[0xFFFF + 1];
	private static final int[] lookup0x1110 = new int[0xFFFF + 1];

	/**
	 * Generate lookup for each of the 16 cells.
	 */
	public static void setTransformRule(Rule rule) {
		if (rule.equals(lookupRule)) {
			return;
		}
		lookupRule = rule;

		// inner 4 bits
		generateLookup(
				rule, lookup0x0660,
				new int[]{0x0020, 0x0040, 0x0200, 0x0400},
				new int[]{0x0757, 0x0EAE, 0x7570, 0xEAE0});

		// outer 12 bits
		generateLookup(
				rule, lookup0x0007,
				new int[]{0x0001, 0x0002, 0x0004},
				new int[]{0x3B32, 0x7075, 0xE0EA});
		generateLookup(
				rule, lookup0x0888,
				new int[]{0x0008, 0x0080, 0x0800},
				new int[]{0x22F5, 0x0D5D, 0xD5D0});
		generateLookup(
				rule, lookup0xE000,
				new int[]{0x2000, 0x4000, 0x8000},
				new int[]{0x5707, 0xAE0E, 0x4CDC});
		generateLookup(
				rule, lookup0x1110,
				new int[]{0x0010, 0x0100, 0x1000},
				new int[]{0x0BAB, 0xBAB0, 0xAF44});
	}

	private static void generateLookup(Rule rule, int[] lookup, int[] bits, int[] neighbors) {
		int mask = 0;
		for (int i  = 0; i < bits.length; i++) {
			mask |= bits[i];
		}

		for (int from = 0; from <= 0xFFFF; from++) {
			int to = from;
			for (int j  = 0; j < bits.length; j++) {
				int bit = bits[j];
				int neighborhood = neighbors[j];

				int bitCount = bitCounts[from & neighborhood];
				if ((from & bit) == bit) {
					 if (!rule.survives(bitCount)) {
						to ^= bit;
					 }
				}
				else {
					if (rule.born(bitCount)) {
						to ^= bit;
					}
				}
			}
			lookup[from] = to & mask;
		}
	}

}
